package com.googlecode.jtoy.jee.jaxb.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;

import javax.xml.XMLConstants;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationEventLocator;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import com.googlecode.jtoy.jee.jaxb.Jaxb;
import com.googlecode.jtoy.jee.jaxb.Message;

/**
 * 本类用于xml验证、将xml转化成java对象（解组）和将java对象转化成xml（编组）
 * 
 * @author shangxj
 * @date 2011-4-10 下午07:54:36
 */
public class JaxbImpl implements Jaxb {

	private static final Logger logger = LoggerFactory.getLogger(JaxbImpl.class);

	/*
	 * javax.xml.constants 类定义了几个常量来标识模式语言：
	 * XMLConstants.W3C_XML_SCHEMA_NS_URI：http://www.w3.org/2001/XMLSchema
	 * XMLConstants.RELAXNG_NS_URI：http://relaxng.org/ns/structure/1.0
	 * XMLConstants.XML_DTD_NS_URI：http://www.w3.org/TR/REC-xml
	 */
	private final static SchemaFactory factory = SchemaFactory
			.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);

	/**
	 * 错误消息
	 */
	private List<Message> messages = new ArrayList<Message>();

	/**
	 * 根据Schema验证xml格式是否正确
	 */
	private Validator validator;

	/**
	 * 编组对象成xml
	 */
	private Marshaller marshaller;
	/**
	 * 解组xml成对象
	 */
	private Unmarshaller unmarshaller;

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#setSchema(java.lang.String)
	 */
	public JaxbImpl(String schema, String clazz) throws SAXException, ClassNotFoundException,
			JAXBException {

		String path = JaxbImpl.class.getClassLoader().getResource("").getPath();

		Schema sch = factory.newSchema(new File(path + schema));

		validator = sch.newValidator();

		JAXBContext context = JAXBContext.newInstance(Class.forName(clazz));

		unmarshaller = context.createUnmarshaller();
		unmarshaller.setSchema(sch);
		unmarshaller.setEventHandler(new JaxbEventHandler());

		marshaller = context.createMarshaller();
		// 编码格式
		marshaller.setProperty(Marshaller.JAXB_ENCODING, "utf-8");
		// 是否格式化生成的xml串
		marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
		// 是否省略xml头信息（<?xml version="1.0" encoding="utf-8" standalone="yes"?>）
		marshaller.setProperty(Marshaller.JAXB_FRAGMENT, false);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.Jaxb#validate(java.io.Reader)
	 */
	@Override
	public synchronized boolean validate(Reader reader) {

		logger.debug(">>>: validating input xml reader...");

		Source source = new StreamSource(reader);
		try {
			validator.validate(source);
		}
		catch (Exception e) {
			logger.error("!!!: xml format not valid, {}" + e.getMessage());
			return false;
		}

		logger.debug("<<<: validate completed, xml is valid, congratulations.");
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#validate(java.io.InputStream)
	 */
	@Override
	public synchronized boolean validate(InputStream inStream) {

		logger.debug(">>>: validating input xml stream...");

		Source source = new StreamSource(inStream);
		try {
			validator.validate(source);
		}
		catch (Exception e) {
			logger.error("!!!: xml format not valid, {}" + e.getMessage());
			return false;
		}

		logger.debug("<<<: validate completed, xml is valid, congratulations.");
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#marshal(T)
	 */
	@Override
	public synchronized <T> StringWriter marshal(T t) throws JAXBException {

		logger.debug(">>>: marshalling java bean to xml...");

		if (t == null) {
			return null;
		}

		StringWriter out = new StringWriter();
		marshaller.marshal(t, out);

		logger.debug("<<<: marshal completed.");
		return out;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.Jaxb#unmarshal(java.io.Reader)
	 */
	@Override
	public synchronized <T> T unmarshal(Reader reader) throws JAXBException, IOException {

		logger.debug(">>>: unmarshalling java bean to xml...");

		@SuppressWarnings("unchecked")
		T t = (T) unmarshaller.unmarshal(reader);

		logger.debug("<<<: unmarshal completed.");

		return t;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#unmarshal(java.io.InputStream)
	 */
	@Override
	public synchronized <T> T unmarshal(InputStream inStream) throws JAXBException, IOException {

		logger.debug(">>>: unmarshalling java bean to xml...");

		@SuppressWarnings("unchecked")
		T t = (T) unmarshaller.unmarshal(inStream);

		logger.debug("<<<: unmarshal completed.");

		return t;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#unmarshalWithValidate(java.io.InputStream)
	 */
	@Override
	@SuppressWarnings({ "unchecked", "finally" })
	public synchronized <T> T unmarshalWithValidate(Reader reader) {

		logger.debug(">>>: unmarshalling with validate from input xml stream...");

		this.messages.clear();

		T t = null;
		try {
			t = (T) unmarshaller.unmarshal(reader);
			logger.debug("<<<: unmarshal completed, errors if have will be wrapped in messages.");
		}
		catch (Exception e) {
			logger.error("!!!: errors be wrapped in messages.");
		}
		finally {
			return t;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.Jaxb#unmarshalWithValidate(java.io.InputStream)
	 */
	@Override
	@SuppressWarnings({ "unchecked", "finally" })
	public synchronized <T> T unmarshalWithValidate(InputStream inStream) {

		logger.debug(">>>: unmarshalling with validate from input xml stream...");

		this.messages.clear();

		T t = null;
		try {
			t = (T) unmarshaller.unmarshal(inStream);
			logger.debug("<<<: unmarshal completed, errors if have will be wrapped in messages.");
		}
		catch (Exception e) {
			logger.error("---: errors be wrapped in messages.");
		}
		finally {
			return t;
		}
	}

	/**
	 * 收集错误信息
	 * 
	 * @author shangxj
	 * @date Apr 17, 2011 4:03:10 PM
	 */
	class JaxbEventHandler implements ValidationEventHandler {

		@Override
		public boolean handleEvent(ValidationEvent event) {

			if (event.getSeverity() != ValidationEvent.WARNING) {
				ValidationEventLocator locator = event.getLocator();
				messages.add(new Message(event.getSeverity(), locator.getLineNumber(), locator
						.getColumnNumber(), event.getMessage()));
			}
			return true;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#getMessages()
	 */
	@Override
	public List<Message> getMessages() {

		return this.messages;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.sable.engine.jaxb.JaxbOxm#hasError()
	 */
	@Override
	public boolean hasError() {

		return this.messages.size() > 0;
	}
}
